package org.eclipse.jface.text;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.PatternSyntaxException;

import org.eclipse.core.runtime.ListenerList;

/**
 * Abstract default implementation of <code>IDocument</code> and its extension
 * interfaces {@link org.eclipse.jface.text.IDocumentExtension},
 * {@link org.eclipse.jface.text.IDocumentExtension2},
 * {@link org.eclipse.jface.text.IDocumentExtension3},
 * {@link org.eclipse.jface.text.IDocumentExtension4}, as well as
 * {@link org.eclipse.jface.text.IRepairableDocument}.
 * <p>
 * 
 * An <code>AbstractDocument</code> supports the following implementation
 * plug-ins:
 * <ul>
 * <li>a text store implementing {@link org.eclipse.jface.text.ITextStore} for
 * storing and managing the document's content,</li>
 * <li>a line tracker implementing {@link org.eclipse.jface.text.ILineTracker}
 * to map character positions to line numbers and vice versa</li>
 * </ul>
 * The document can dynamically change the text store when switching between
 * sequential rewrite mode and normal mode.
 * <p>
 * 
 * This class must be subclassed. Subclasses must configure which implementation
 * plug-ins the document instance should use. Subclasses are not intended to
 * overwrite existing methods.
 * 
 * @see org.eclipse.jface.text.ITextStore
 * @see org.eclipse.jface.text.ILineTracker
 */
public abstract class AbstractDocument implements IDocument,
		IDocumentExtension, IDocumentExtension4 {
	
	/**
	 * The identifier of the default partitioning.
	 */
	final static String DEFAULT_PARTITIONING= "__dftl_partitioning"; //$NON-NLS-1$

	/**
	 * Tells whether this class is in debug mode.
	 * 
	 * @since 3.1
	 */
	private static final boolean DEBUG = false;

	/**
	 * Inner class to bundle a registered post notification replace operation
	 * together with its owner.
	 * 
	 * @since 2.0
	 */
	static private class RegisteredReplace {
		/** The owner of this replace operation. */
		IDocumentListener fOwner;

		/** The replace operation */
		IDocumentExtension.IReplace fReplace;

		/**
		 * Creates a new bundle object.
		 * 
		 * @param owner
		 *            the document listener owning the replace operation
		 * @param replace
		 *            the replace operation
		 */
		RegisteredReplace(IDocumentListener owner,
				IDocumentExtension.IReplace replace) {
			fOwner = owner;
			fReplace = replace;
		}
	}

	/** The document's text store */
	private ITextStore fStore;

	/** The document's line tracker */
	private ILineTracker fTracker;

	/** The registered document listeners */
	private ListenerList fDocumentListeners;

	/** The registered pre-notified document listeners */
	private ListenerList fPrenotifiedDocumentListeners;

	/** The registered document partitioning listeners */
	private ListenerList fDocumentPartitioningListeners;

	/** All positions managed by the document */
	private Map fPositions;

	/** All registered document position updaters */
	private List fPositionUpdaters;

	/**
	 * The list of post notification changes
	 * 
	 * @since 2.0
	 */
	private List fPostNotificationChanges;

	/**
	 * The reentrance count for post notification changes.
	 * 
	 * @since 2.0
	 */
	private int fReentranceCount = 0;

	/**
	 * Indicates whether post notification change processing has been stopped.
	 * 
	 * @since 2.0
	 */
	private int fStoppedCount = 0;

	/**
	 * Indicates whether the registration of post notification changes should be
	 * ignored.
	 * 
	 * @since 2.1
	 */
	private boolean fAcceptPostNotificationReplaces = true;

	/**
	 * Indicates whether the notification of listeners has been stopped.
	 * 
	 * @since 2.1
	 */
	private int fStoppedListenerNotification = 0;

	/**
	 * The document event to be sent after listener notification has been
	 * resumed.
	 * 
	 * @since 2.1
	 */
	private DocumentEvent fDeferredDocumentEvent;

	/**
	 * The registered document partitioners.
	 * 
	 * @since 3.0
	 */
	private Map fDocumentPartitioners;

	/**
	 * The find/replace document adapter.
	 * 
	 * @since 3.0
	 */
	private FindReplaceDocumentAdapter fFindReplaceDocumentAdapter;

	/**
	 * The active document rewrite session.
	 * 
	 * @since 3.1
	 */
	private DocumentRewriteSession fDocumentRewriteSession;

	/**
	 * The registered document rewrite session listeners.
	 * 
	 * @since 3.1
	 */
	private List fDocumentRewriteSessionListeners;

	/**
	 * The current modification stamp.
	 * 
	 * @since 3.1
	 */
	private long fModificationStamp = IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;

	/**
	 * Keeps track of next modification stamp.
	 * 
	 * @since 3.1.1
	 */
	private long fNextModificationStamp = IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;

	/**
	 * This document's default line delimiter.
	 * 
	 * @since 3.1
	 */
	private String fInitialLineDelimiter;

	/**
	 * The default constructor does not perform any configuration but leaves it
	 * to the clients who must first initialize the implementation plug-ins and
	 * then call <code>completeInitialization</code>. Results in the
	 * construction of an empty document.
	 */
	protected AbstractDocument() {
		fModificationStamp = getNextModificationStamp();
	}

	/**
	 * Returns the document's text store. Assumes that the document has been
	 * initialized with a text store.
	 * 
	 * @return the document's text store
	 */
	protected ITextStore getStore() {
		return fStore;
	}

	/**
	 * Returns the document's line tracker. Assumes that the document has been
	 * initialized with a line tracker.
	 * 
	 * @return the document's line tracker
	 */
	protected ILineTracker getTracker() {
		return fTracker;
	}

	/**
	 * Returns the document's document listeners.
	 * 
	 * @return the document's document listeners
	 */
	protected List getDocumentListeners() {
		return Arrays.asList(fDocumentListeners.getListeners());
	}

	/**
	 * Returns the document's partitioning listeners.
	 * 
	 * @return the document's partitioning listeners
	 */
	protected List getDocumentPartitioningListeners() {
		return Arrays.asList(fDocumentPartitioningListeners.getListeners());
	}

	/**
	 * Returns all positions managed by the document grouped by category.
	 * 
	 * @return the document's positions
	 */
	protected Map getDocumentManagedPositions() {
		return fPositions;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getDocumentPartitioner()
	 */
	public IDocumentPartitioner getDocumentPartitioner() {
		return null;
	}

	// --- implementation configuration interface ------------

	/**
	 * Sets the document's text store. Must be called at the beginning of the
	 * constructor.
	 * 
	 * @param store
	 *            the document's text store
	 */
	protected void setTextStore(ITextStore store) {
		fStore = store;
	}

	/**
	 * Sets the document's line tracker. Must be called at the beginning of the
	 * constructor.
	 * 
	 * @param tracker
	 *            the document's line tracker
	 */
	protected void setLineTracker(ILineTracker tracker) {
		fTracker = tracker;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#setDocumentPartitioner(org.eclipse.jface.text.IDocumentPartitioner)
	 */
	public void setDocumentPartitioner(IDocumentPartitioner partitioner) {
//		setDocumentPartitioner(DEFAULT_PARTITIONING, partitioner);
	}

	/**
	 * Initializes document listeners, positions, and position updaters. Must be
	 * called inside the constructor after the implementation plug-ins have been
	 * set.
	 */
	protected void completeInitialization() {

		fPositions = new HashMap();
		fPositionUpdaters = new ArrayList();
		fDocumentListeners = new ListenerList();
		fPrenotifiedDocumentListeners = new ListenerList();
		fDocumentPartitioningListeners = new ListenerList();
		fDocumentRewriteSessionListeners = new ArrayList();

		addPositionCategory(DEFAULT_CATEGORY);
//		addPositionUpdater(new DefaultPositionUpdater(DEFAULT_CATEGORY));
	}

	// -------------------------------------------------------

	/*
	 * @see org.eclipse.jface.text.IDocument#addDocumentListener(org.eclipse.jface.text.IDocumentListener)
	 */
	public void addDocumentListener(IDocumentListener listener) {
		fDocumentListeners.add(listener);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#removeDocumentListener(org.eclipse.jface.text.IDocumentListener)
	 */
	public void removeDocumentListener(IDocumentListener listener) {
		fDocumentListeners.remove(listener);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#addPrenotifiedDocumentListener(org.eclipse.jface.text.IDocumentListener)
	 */
	public void addPrenotifiedDocumentListener(IDocumentListener listener) {
		fPrenotifiedDocumentListeners.add(listener);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#removePrenotifiedDocumentListener(org.eclipse.jface.text.IDocumentListener)
	 */
	public void removePrenotifiedDocumentListener(IDocumentListener listener) {
		fPrenotifiedDocumentListeners.remove(listener);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#addDocumentPartitioningListener(org.eclipse.jface.text.IDocumentPartitioningListener)
	 */
	public void addDocumentPartitioningListener(
			IDocumentPartitioningListener listener) {
		fDocumentPartitioningListeners.add(listener);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#removeDocumentPartitioningListener(org.eclipse.jface.text.IDocumentPartitioningListener)
	 */
	public void removeDocumentPartitioningListener(
			IDocumentPartitioningListener listener) {
		fDocumentPartitioningListeners.remove(listener);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#addPosition(java.lang.String,
	 *      org.eclipse.jface.text.Position)
	 */
	public void addPosition(String category, Position position)
			throws BadLocationException, BadPositionCategoryException {

		if ((0 > position.offset) || (0 > position.length)
				|| (position.offset + position.length > getLength()))
			throw new BadLocationException();

		if (category == null)
			throw new BadPositionCategoryException();

		List list = (List) fPositions.get(category);
		if (list == null)
			throw new BadPositionCategoryException();

		list.add(computeIndexInPositionList(list, position.offset), position);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#addPosition(org.eclipse.jface.text.Position)
	 */
	public void addPosition(Position position) throws BadLocationException {
		try {
			addPosition(DEFAULT_CATEGORY, position);
		} catch (BadPositionCategoryException e) {
		}
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#addPositionCategory(java.lang.String)
	 */
	public void addPositionCategory(String category) {

		if (category == null)
			return;

		if (!containsPositionCategory(category))
			fPositions.put(category, new ArrayList());
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#addPositionUpdater(org.eclipse.jface.text.IPositionUpdater)
	 */
	public void addPositionUpdater(IPositionUpdater updater) {
		insertPositionUpdater(updater, fPositionUpdaters.size());
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#containsPosition(java.lang.String,
	 *      int, int)
	 */
	public boolean containsPosition(String category, int offset, int length) {

		if (category == null)
			return false;

		List list = (List) fPositions.get(category);
		if (list == null)
			return false;

		int size = list.size();
		if (size == 0)
			return false;

		int index = computeIndexInPositionList(list, offset);
		if (index < size) {
			Position p = (Position) list.get(index);
			while (p != null && p.offset == offset) {
				if (p.length == length)
					return true;
				++index;
				p = (index < size) ? (Position) list.get(index) : null;
			}
		}

		return false;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#containsPositionCategory(java.lang.String)
	 */
	public boolean containsPositionCategory(String category) {
		if (category != null)
			return fPositions.containsKey(category);
		return false;
	}

	/**
	 * Computes the index in the list of positions at which a position with the
	 * given offset would be inserted. The position is supposed to become the
	 * first in this list of all positions with the same offset.
	 * 
	 * @param positions
	 *            the list in which the index is computed
	 * @param offset
	 *            the offset for which the index is computed
	 * @return the computed index
	 * 
	 * @see IDocument#computeIndexInCategory(String, int)
	 */
	protected int computeIndexInPositionList(List positions, int offset) {

		if (positions.size() == 0)
			return 0;

		int left = 0;
		int right = positions.size() - 1;
		int mid = 0;
		Position p = null;

		while (left < right) {

			mid = (left + right) / 2;

			p = (Position) positions.get(mid);
			if (offset < p.getOffset()) {
				if (left == mid)
					right = left;
				else
					right = mid - 1;
			} else if (offset > p.getOffset()) {
				if (right == mid)
					left = right;
				else
					left = mid + 1;
			} else if (offset == p.getOffset()) {
				left = right = mid;
			}

		}

		int pos = left;
		p = (Position) positions.get(pos);
		if (offset > p.getOffset()) {
			// append to the end
			pos++;
		} else {
			// entry will became the first of all entries with the same offset
			do {
				--pos;
				if (pos < 0)
					break;
				p = (Position) positions.get(pos);
			} while (offset == p.getOffset());
			++pos;
		}

		return pos;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#computeIndexInCategory(java.lang.String,
	 *      int)
	 */
	public int computeIndexInCategory(String category, int offset)
			throws BadLocationException, BadPositionCategoryException {

		if (0 > offset || offset > getLength())
			throw new BadLocationException();

		List c = (List) fPositions.get(category);
		if (c == null)
			throw new BadPositionCategoryException();

		return computeIndexInPositionList(c, offset);
	}

	/**
	 * Fires the document partitioning changed notification to all registered
	 * document partitioning listeners. Uses a robust iterator.
	 * 
	 * @deprecated as of 2.0. Use
	 *             <code>fireDocumentPartitioningChanged(IRegion)</code>
	 *             instead.
	 */
	protected void fireDocumentPartitioningChanged() {
		if (fDocumentPartitioningListeners == null)
			return;

		Object[] listeners = fDocumentPartitioningListeners.getListeners();
		for (int i = 0; i < listeners.length; i++)
			((IDocumentPartitioningListener) listeners[i])
					.documentPartitioningChanged(this);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getChar(int)
	 */
	public char getChar(int pos) throws BadLocationException {
		if ((0 > pos) || (pos >= getLength()))
			throw new BadLocationException();
		return getStore().get(pos);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getContentType(int)
	 */
	public String getContentType(int offset) throws BadLocationException {
		String contentType = null;
//		try {
//			contentType = getContentType(DEFAULT_PARTITIONING, offset, false);
//		} catch (BadPartitioningException e) {
//		}
		return contentType;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLegalContentTypes()
	 */
	public String[] getLegalContentTypes() {
		String[] contentTypes = null;
//		try {
//			contentTypes = getLegalContentTypes(DEFAULT_PARTITIONING);
//		} catch (BadPartitioningException e) {
//		}
		return contentTypes;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLength()
	 */
	public int getLength() {
		return getStore().getLength();
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLineDelimiter(int)
	 */
	public String getLineDelimiter(int line) throws BadLocationException {
		return getTracker().getLineDelimiter(line);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLegalLineDelimiters()
	 */
	public String[] getLegalLineDelimiters() {
		return getTracker().getLegalLineDelimiters();
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension4#getDefaultLineDelimiter()
	 * @since 3.1
	 */
	public String getDefaultLineDelimiter() {

		String lineDelimiter = null;

		try {
			lineDelimiter = getLineDelimiter(0);
		} catch (BadLocationException x) {
		}

		if (lineDelimiter != null)
			return lineDelimiter;

		if (fInitialLineDelimiter != null)
			return fInitialLineDelimiter;

		String sysLineDelimiter = System.getProperty("line.separator"); //$NON-NLS-1$
		String[] delimiters = getLegalLineDelimiters();
		for (int i = 0; i < delimiters.length; i++) {
			if (delimiters[i].equals(sysLineDelimiter)) {
				lineDelimiter = sysLineDelimiter;
				break;
			}
		}

		if (lineDelimiter == null)
			lineDelimiter = delimiters[0];

		return lineDelimiter;

	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension4#setInitialLineDelimiter(java.lang.String)
	 * @since 3.1
	 */
	public void setInitialLineDelimiter(String lineDelimiter) {
		fInitialLineDelimiter = lineDelimiter;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLineLength(int)
	 */
	public int getLineLength(int line) throws BadLocationException {
		return getTracker().getLineLength(line);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLineOfOffset(int)
	 */
	public int getLineOfOffset(int pos) throws BadLocationException {
		return getTracker().getLineNumberOfOffset(pos);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLineOffset(int)
	 */
	public int getLineOffset(int line) throws BadLocationException {
		return getTracker().getLineOffset(line);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLineInformation(int)
	 */
	public IRegion getLineInformation(int line) throws BadLocationException {
		return getTracker().getLineInformation(line);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getLineInformationOfOffset(int)
	 */
	public IRegion getLineInformationOfOffset(int offset)
			throws BadLocationException {
		return getTracker().getLineInformationOfOffset(offset);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getNumberOfLines()
	 */
	public int getNumberOfLines() {
		return getTracker().getNumberOfLines();
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getNumberOfLines(int, int)
	 */
	public int getNumberOfLines(int offset, int length)
			throws BadLocationException {
		return getTracker().getNumberOfLines(offset, length);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#computeNumberOfLines(java.lang.String)
	 */
	public int computeNumberOfLines(String text) {
		return getTracker().computeNumberOfLines(text);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getPartition(int)
	 */
	public ITypedRegion getPartition(int offset) throws BadLocationException {
		ITypedRegion partition = null;
		
//			partition = getPartition(DEFAULT_PARTITIONING, offset, false);
		
		return partition;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#computePartitioning(int, int)
	 */
	public ITypedRegion[] computePartitioning(int offset, int length)
			throws BadLocationException {
		ITypedRegion[] partitioning = null;
//		try {
//			partitioning = computePartitioning(DEFAULT_PARTITIONING, offset,
//					length, false);
//
//		} catch (BadPartitioningException e) {
//		}
		return partitioning;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getPositions(java.lang.String)
	 */
	public Position[] getPositions(String category)
			throws BadPositionCategoryException {

		if (category == null)
			throw new BadPositionCategoryException();

		List c = (List) fPositions.get(category);
		if (c == null)
			throw new BadPositionCategoryException();

		Position[] positions = new Position[c.size()];
		c.toArray(positions);
		return positions;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getPositionCategories()
	 */
	public String[] getPositionCategories() {
		String[] categories = new String[fPositions.size()];
		Iterator keys = fPositions.keySet().iterator();
		for (int i = 0; i < categories.length; i++)
			categories[i] = (String) keys.next();
		return categories;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#getPositionUpdaters()
	 */
	public IPositionUpdater[] getPositionUpdaters() {
		IPositionUpdater[] updaters = new IPositionUpdater[fPositionUpdaters
				.size()];
		fPositionUpdaters.toArray(updaters);
		return updaters;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#get()
	 */
	public String get() {
		return getStore().get(0, getLength());
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#get(int, int)
	 */
	public String get(int pos, int length) throws BadLocationException {
		int myLength = getLength();
		if ((0 > pos) || (0 > length) || (pos + length > myLength))
			throw new BadLocationException();
		return getStore().get(pos, length);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#insertPositionUpdater(org.eclipse.jface.text.IPositionUpdater,
	 *      int)
	 */
	public void insertPositionUpdater(IPositionUpdater updater, int index) {

		for (int i = fPositionUpdaters.size() - 1; i >= 0; i--) {
			if (fPositionUpdaters.get(i) == updater)
				return;
		}

		if (index == fPositionUpdaters.size())
			fPositionUpdaters.add(updater);
		else
			fPositionUpdaters.add(index, updater);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#removePosition(java.lang.String,
	 *      org.eclipse.jface.text.Position)
	 */
	public void removePosition(String category, Position position)
			throws BadPositionCategoryException {

		if (position == null)
			return;

		if (category == null)
			throw new BadPositionCategoryException();

		List c = (List) fPositions.get(category);
		if (c == null)
			throw new BadPositionCategoryException();

		// remove based on identity not equality
		int size = c.size();
		for (int i = 0; i < size; i++) {
			if (position == c.get(i)) {
				c.remove(i);
				return;
			}
		}
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#removePosition(org.eclipse.jface.text.Position)
	 */
	public void removePosition(Position position) {
		try {
			removePosition(DEFAULT_CATEGORY, position);
		} catch (BadPositionCategoryException e) {
		}
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#removePositionCategory(java.lang.String)
	 */
	public void removePositionCategory(String category)
			throws BadPositionCategoryException {

		if (category == null)
			return;

		if (!containsPositionCategory(category))
			throw new BadPositionCategoryException();

		fPositions.remove(category);
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#removePositionUpdater(org.eclipse.jface.text.IPositionUpdater)
	 */
	public void removePositionUpdater(IPositionUpdater updater) {
		for (int i = fPositionUpdaters.size() - 1; i >= 0; i--) {
			if (fPositionUpdaters.get(i) == updater) {
				fPositionUpdaters.remove(i);
				return;
			}
		}
	}

	private long getNextModificationStamp() {
		if (fNextModificationStamp == Long.MAX_VALUE
				|| fNextModificationStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP)
			fNextModificationStamp = 0;
		else
			fNextModificationStamp = fNextModificationStamp + 1;

		return fNextModificationStamp;
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension4#getModificationStamp()
	 * @since 3.1
	 */
	public long getModificationStamp() {
		return fModificationStamp;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#replace(int, int, java.lang.String)
	 * @since 3.1
	 */
	public void replace(int pos, int length, String text, long modificationStamp)
			throws BadLocationException {
		if ((0 > pos) || (0 > length) || (pos + length > getLength()))
			throw new BadLocationException();

		DocumentEvent e = new DocumentEvent(this, pos, length, text);

		getStore().replace(pos, length, text);
		getTracker().replace(pos, length, text);

		fModificationStamp = modificationStamp;
		fNextModificationStamp = Math.max(fModificationStamp,
				fNextModificationStamp);
		e.fModificationStamp = fModificationStamp;
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#replace(int, int, java.lang.String)
	 */
	public void replace(int pos, int length, String text)
			throws BadLocationException {
		if (length == 0 && (text == null || text.length() == 0))
			replace(pos, length, text, getModificationStamp());
		else
			replace(pos, length, text, getNextModificationStamp());
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#set(java.lang.String)
	 */
	public void set(String text) {
		set(text, getNextModificationStamp());
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#set(java.lang.String, long)
	 */
	public void set(String text, long modificationStamp) {
		int length = getStore().getLength();

		DocumentEvent e = new DocumentEvent(this, 0, length, text);

		getStore().set(text);
		getTracker().set(text);

		fModificationStamp = modificationStamp;
		fNextModificationStamp = Math.max(fModificationStamp,
				fNextModificationStamp);
		e.fModificationStamp = fModificationStamp;

	}

	/**
	 * Updates all positions of all categories to the change described by the
	 * document event. All registered document updaters are called in the
	 * sequence they have been arranged. Uses a robust iterator.
	 * 
	 * @param event
	 *            the document event describing the change to which to adapt the
	 *            positions
	 */
	protected void updatePositions(DocumentEvent event) {
		List list = new ArrayList(fPositionUpdaters);
		Iterator e = list.iterator();
		while (e.hasNext()) {
			IPositionUpdater u = (IPositionUpdater) e.next();
			u.update(event);
		}
	}

	/*
	 * @see org.eclipse.jface.text.IDocument#search(int, java.lang.String,
	 *      boolean, boolean, boolean)
	 */
	public int search(int startPosition, String findString,
			boolean forwardSearch, boolean caseSensitive, boolean wholeWord)
			throws BadLocationException {
		try {
			IRegion region = getFindReplaceDocumentAdapter().find(
					startPosition, findString, forwardSearch, caseSensitive,
					wholeWord, false);
			return region == null ? -1 : region.getOffset();
		} catch (IllegalStateException ex) {
			return -1;
		} catch (PatternSyntaxException ex) {
			return -1;
		}
	}

	/**
	 * Returns the find/replace adapter for this document.
	 * 
	 * @return this document's find/replace document adapter
	 * @since 3.0
	 */
	private FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() {
		if (fFindReplaceDocumentAdapter == null)
			fFindReplaceDocumentAdapter = new FindReplaceDocumentAdapter(this);

		return fFindReplaceDocumentAdapter;
	}

	/**
	 * Flushes all registered post notification changes.
	 * 
	 * @since 2.0
	 */
	private void flushPostNotificationChanges() {
		if (fPostNotificationChanges != null)
			fPostNotificationChanges.clear();
	}

	/**
	 * Executes all registered post notification changes. The process is
	 * repeated until no new post notification changes are added.
	 * 
	 * @since 2.0
	 */
	private void executePostNotificationChanges() {

		if (fStoppedCount > 0)
			return;

		while (fPostNotificationChanges != null) {
			List changes = fPostNotificationChanges;
			fPostNotificationChanges = null;

			Iterator e = changes.iterator();
			while (e.hasNext()) {
				RegisteredReplace replace = (RegisteredReplace) e.next();
				replace.fReplace.perform(this, replace.fOwner);
			}
		}
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension2#acceptPostNotificationReplaces()
	 * @since 2.1
	 */
	public void acceptPostNotificationReplaces() {
		fAcceptPostNotificationReplaces = true;
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension2#ignorePostNotificationReplaces()
	 * @since 2.1
	 */
	public void ignorePostNotificationReplaces() {
		fAcceptPostNotificationReplaces = false;
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension#stopPostNotificationProcessing()
	 * @since 2.0
	 */
	public void stopPostNotificationProcessing() {
		++fStoppedCount;
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension#resumePostNotificationProcessing()
	 * @since 2.0
	 */
	public void resumePostNotificationProcessing() {
		--fStoppedCount;
		if (fStoppedCount == 0 && fReentranceCount == 0)
			executePostNotificationChanges();
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension#startSequentialRewrite(boolean)
	 * @since 2.0
	 */
	public void startSequentialRewrite(boolean normalized) {
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension#stopSequentialRewrite()
	 * @since 2.0
	 */
	public void stopSequentialRewrite() {
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension2#resumeListenerNotification()
	 * @since 2.1
	 */
	public void resumeListenerNotification() {
		--fStoppedListenerNotification;
		if (fStoppedListenerNotification == 0) {

		}
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension2#stopListenerNotification()
	 * @since 2.1
	 */
	public void stopListenerNotification() {
		++fStoppedListenerNotification;
	}

	/**
	 * Fires the given event to all registered rewrite session listeners. Uses
	 * robust iterators.
	 * 
	 * @param event
	 *            the event to be fired
	 * @since 3.1
	 */
	protected void fireRewriteSessionChanged(DocumentRewriteSessionEvent event) {
		if (fDocumentRewriteSessionListeners.size() > 0) {
			List list = new ArrayList(fDocumentRewriteSessionListeners);
			Iterator e = list.iterator();
			while (e.hasNext()) {
				IDocumentRewriteSessionListener l = (IDocumentRewriteSessionListener) e
						.next();
				l.documentRewriteSessionChanged(event);
			}
		}
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension4#getActiveRewriteSession()
	 */
	public final DocumentRewriteSession getActiveRewriteSession() {
		return fDocumentRewriteSession;
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension4#startRewriteSession(org.eclipse.jface.text.DocumentRewriteSessionType)
	 * @since 3.1
	 */
	public DocumentRewriteSession startRewriteSession(
			DocumentRewriteSessionType sessionType) {

		if (getActiveRewriteSession() != null)
			throw new IllegalStateException();

		fDocumentRewriteSession = new DocumentRewriteSession(sessionType);
		if (DEBUG)
			System.out
					.println("AbstractDocument: Starting rewrite session: " + fDocumentRewriteSession); //$NON-NLS-1$

		fireRewriteSessionChanged(new DocumentRewriteSessionEvent(this,
				fDocumentRewriteSession,
				DocumentRewriteSessionEvent.SESSION_START));

		ILineTracker tracker = getTracker();

		if (DocumentRewriteSessionType.SEQUENTIAL == sessionType)
			startSequentialRewrite(false);
		else if (DocumentRewriteSessionType.STRICTLY_SEQUENTIAL == sessionType)
			startSequentialRewrite(true);

		return fDocumentRewriteSession;
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension4#stopRewriteSession(org.eclipse.jface.text.DocumentRewriteSession)
	 * @since 3.1
	 */
	public void stopRewriteSession(DocumentRewriteSession session) {
		if (fDocumentRewriteSession == session) {

			if (DEBUG)
				System.out
						.println("AbstractDocument: Stopping rewrite session: " + session); //$NON-NLS-1$

			DocumentRewriteSessionType sessionType = session.getSessionType();
			if (DocumentRewriteSessionType.SEQUENTIAL == sessionType
					|| DocumentRewriteSessionType.STRICTLY_SEQUENTIAL == sessionType)
				stopSequentialRewrite();

			ILineTracker tracker = getTracker();

			fDocumentRewriteSession = null;
			fireRewriteSessionChanged(new DocumentRewriteSessionEvent(this,
					session, DocumentRewriteSessionEvent.SESSION_STOP));
		}
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension4#addDocumentRewriteSessionListener(org.eclipse.jface.text.IDocumentRewriteSessionListener)
	 * @since 3.1
	 */
	public void addDocumentRewriteSessionListener(
			IDocumentRewriteSessionListener listener) {
		if (!fDocumentRewriteSessionListeners.contains(listener))
			fDocumentRewriteSessionListeners.add(listener);
	}

	/*
	 * @see org.eclipse.jface.text.IDocumentExtension4#removeDocumentRewriteSessionListener(org.eclipse.jface.text.IDocumentRewriteSessionListener)
	 * @since 3.1
	 */
	public void removeDocumentRewriteSessionListener(
			IDocumentRewriteSessionListener listener) {
		fDocumentRewriteSessionListeners.remove(listener);
	}

}
